/*
 * This file is part of Xer.
 *
 * Simple interpreter for the X language
 * Copyright (C)  Michael Morckos 2009 <mikey.morckos@gmail.com>
 *
 * Xer is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Xer is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Xer.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "X_parser.h"
#include "X_keywords.h"
//#include "X_lexer.h"

void init() {
	symbol_table = malloc(sizeof(Token) * 30000);
	currC = '\0'; 
	tmp = '\0';
	pointer = 0;
	linecount = 0;
	tokencount = 1;
	stmtcount = 0;
	num = 0;
	errflag = 0;
	lexerflag = 1;
	lexerdone = 0;
	globalflag = 1;
	globalcount = 9;
}

/***************************** UTILITIES *****************************/

void monitor() {
	short orig = globalcount;
	short counter = 9;
	while(counter > 0) {
		if(globalcount != orig) {
			orig = globalcount;
			counter = 9;
		}
		else {
			counter--;
		}	
	}
	globalflag = 1;
	globalcount = 9;
	shift();	
	prg();			
}

void togglethread() {
	globalflag = 0;
	thread_status = pthread_create(&thread, NULL, monitor, NULL);	
}

unsigned char helper(unsigned char res) {
	if(res == 0) {
		togglethread();
		return 1;
	}	
	else if(res == 2) {
		globalcount--;
		return 1;
	}
	return 0;
}

/***************************** LEXER *****************************/

/** This implementation is tailored for a specific problem **/

void shift() {
	while(strcmp(currlexem, ";") != 0) {
		flushlexem();
		scan();
	}
	flushlexem();
	scan();
}	

void flushlexem() {
	short i;
	for(i = 0; i < 1024; i++) {
		currlexem[i] = '\0';
	}	
	pointer = 0;
	num = 0;
	lexerflag = 1;	
}

void update_symbol_table() {
	Token tk;
	strcpy(tk.lexem, currlexem);
	tk.num_val = num;
	if(!strcmp(currlexem, _LET) || !strcmp(currlexem, _LETREC) || !strcmp(currlexem, _FUNC) ||
		!strcmp(currlexem, _IF) || !strcmp(currlexem, _ELSE) || !strcmp(currlexem, _IN)) {
		strcpy(tk.type, _KEYWORD);
	}
	else if(!strcmp(currlexem, _NOT) || !strcmp(currlexem, _GETSTR) || 
			!strcmp(currlexem, _GETINT) || !strcmp(currlexem, _PRINT)) {
			strcpy(tk.type, _FUNCTION);
	}		
	else if(currlexem[0] == '"') {
		strcpy(tk.type, _STRLITERAL);
	}
	else if(isdigit(currlexem[0])) {
		strcpy(tk.type, _NUMLITERAL);
	}
	else if(!strcmp(currlexem, "&&")) {
		strcpy(tk.type, _CA);
	}
	else if(!strcmp(currlexem, "||")) {
		strcpy(tk.type, _CO);
	}
	else if(currlexem[0] == '=') {
		if(strlen(currlexem) == 1) {
			strcpy(tk.type, _AO);
		}
		else {
			strcpy(tk.type, _EQ);
		}		
	}
	else {
		switch(currlexem[0]) {
			case '+' : strcpy(tk.type, _PO); break;
			case '-' : strcpy(tk.type, _MO); break;
			case '*' : strcpy(tk.type, _MB); break;
			case '/' : strcpy(tk.type, _DB); break;
			case ',' : strcpy(tk.type, _FA); break;
			case ';' : strcpy(tk.type, _SM); break;
			case '>' : strcpy(tk.type, _GT); break;
			case '<' : strcpy(tk.type, _LT); break;
			case '{' : strcpy(tk.type, _LC); break;
			case '}' : strcpy(tk.type, _RC); break;
			case '(' : strcpy(tk.type, _LB); break;
			case ')' : strcpy(tk.type, _RB); break;
			default : strcpy(tk.type, _IDENTIFIER); break;
		}	
	}
	symbol_table[tokencount - 1] = tk;
}	

unsigned char matchtoken(char *token) {
	if(globalflag == 0) {
		return 2;
	}	
	if(strcmp(currlexem, token) == 0) {
		flushlexem();
		scan();
		return 1;
	}
	else {
		fprintf(output_parser, "Expected: ");
		fprintf(output_parser, token);
		fprintf(output_parser, "\nFound: ");
		fprintf(output_parser, currlexem);
		fprintf(output_parser, "\nParsing continued from next statement.\n\n");
		flushlexem();
		return 0;
	}		
}

void error(unsigned char flag) {
	if(flag) {
		fprintf(output_parser, "Unmatched Input (");
		fprintf(output_parser, "%c", currC);
		fprintf(output_parser, "). Erroneous Character Ignored.\n");
	}
	else {
		fprintf(output_parser, "Invalid String Literal. Erroneous character ignored.\n");
	}
}

void scan() {
	while(lexerflag == 1) {
		currC = fgetc(input_file);
		if(currC == EOF) {
			lexerflag = 0;
		}	
		if(!isdigit(currC) && currlexem[0] != '\0' && isdigit(currlexem[0])) {
			ungetc(currC, input_file);
			break;
		}
		if(currC != '\n' && currC != '\t' && currC != ' ' && 
			currC != '\f' && currC != '\b' && currC != '\r') {
			if(currC == '+' || currC == '-' || currC == '*' || currC == '/' ||
				currC == ',' || currC == ';' || currC == '>' || currC == '<' || 
				currC == '{' || currC == '}' || currC == '(' || currC == ')') {
				if(currlexem[0] == '"') {
					currlexem[pointer++] = currC;
					if(currlexem[0] == currC && strlen(currlexem) > 1) {
						lexerflag = 0;
					}
				}		
				else if(currlexem[0] != '\0') {
					ungetc(currC, input_file);
					lexerflag = 0;
				}
				else {
					currlexem[0] = currC;
					lexerflag = 0;
				}	
			}
			else if(currC == '=' || currC == '|' || currC == '&') {
				if(currlexem[0] == '"') {
					currlexem[pointer++] = currC;
				}
				else if(currlexem[0] == '\0') {
					currlexem[pointer++] = currC;
					currC = fgetc(input_file);
					if(currlexem[0] == currC) {
						currlexem[pointer++] = currC;
					}
					else {
						ungetc(currC, input_file); 
						if(currlexem[0] == '|' || currlexem[0] == '&') {
							if(currlexem[0] == '|') {
								fprintf(output_parser, "Logical OR Not Supported. Missing Character Added Automatically.\n");
							}
							else {
								fprintf(output_parser, "Logical AND Not Supported. Missing Character Added Automatically.\n");
							}
							currlexem[1] = currlexem[0];
						}
					}
					lexerflag = 0;
				}
				else {
					ungetc(currC, input_file);
					lexerflag = 0;
				}	
			}
			else if(currC == '"') {
				currlexem[pointer++] = currC;
				if(currlexem[0] == currC && strlen(currlexem) > 1) {
					lexerflag = 0;
					printf("DONE %s \n", currlexem); 
				}			
			}
			else if (isdigit(currC) || isalpha(currC) || currC == '_') {
				if(currlexem[0] == '"') {
					currlexem[pointer++] = currC;
				}
				else if(isdigit(currC) && (strlen(currlexem) == 0 || isdigit(currlexem[0]))) {
					currlexem[pointer++] = currC;
					num = (num * 10) + (currC - '0');
				}
				else if(!isdigit(currC) && isdigit(currlexem[0])) {
					ungetc(currC, input_file);
					lexerflag = 0;
				}
				else {
					currlexem[pointer++] = currC;
				}			
			}
			else {
				if(currlexem[0] == '"') {
					currlexem[pointer++] = currC;
				}
				else {
					if(currlexem[0] != '\0') {
						lexerflag = 0;
					}	
					error(1);
				}	
			}			
		}
		else {
			if(currC == '\n' || currC == '\r') {
				linecount++;
				if(currlexem[0] == '"') {
					error(0);		
				}
				else if(currlexem[0] != '\0') {
					lexerflag = 0;
				}	
			}
			else if(currC == ' ') {
				if(currlexem[0] == '"') {
					currlexem[pointer++] = currC;
				}
				else if(currlexem[0] != '\0') {
					lexerflag = 0;
				}
			}
			else if(currlexem[0] != '\0') {
				lexerflag = 0;
			}
		}				
	}
	if(currC == EOF) {
		lexerdone = 1;
		if(currlexem[0] == '\0') {
			return;
		}	
	}
//	print();
	tokencount++;
//	update_symbol_table();
}

/***************************** PARSER *****************************/

/*** X Language Syntactic specifications ***/

/*
Program -> Expression EOF

Expression -> Comparison ((&& | ||) Comparison)*

Comparison -> Arithmetic ((== | < | >) Arithmetic)*

Arithmetic -> Term ((+ | –) Term)*

Term -> Primary ((* | /) Primary)*

Primary -> NotFunctionCall Arguments*

NotFunctionCall -> 	( Expression )
		|	let Bindings in Expression
		|	letrec Bindings in Expression
		|	func Parameters { Expression }
		|	if ( Expression ) { Expression } else { Expression }
		|	IDENTIFIER
		|	STRING_LITERAL
		|	INTEGER_LITERAL

Arguments -> ( (Expression (, Expression)*)? )

Parameters -> ( (IDENTIFIER (, IDENTIFIER)*)? )

Bindings -> IDENTIFIER = Expression (, IDENTIFIER = Expression)*

*/

void prg() {
	unsigned char res;
	while(lexerdone == 0) {
		stmtcount++;
		fprintf(output_parser, "[Statement ");
		fprintf(output_parser, "%d", stmtcount);
		fprintf(output_parser, "]\n");
		expr();
		res = matchtoken(";");
		if(res == 1) {
			fprintf(output_parser, "Yes!\n\n");
		}
		if(helper(res))
			return;	
	}	
}

void expr() {
	char tmp[1024];
	unsigned char res;
	compr();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, "&&") == 0 || strcmp(tmp, "||") == 0) {
		res = matchtoken(tmp);
		if(helper(res))
			return;		
		compr();
		strcpy(tmp, currlexem);
	}
}

void compr() {
	char tmp[1024];
	unsigned char res;
	arith();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, "==") == 0 || strcmp(tmp, "<") == 0 || strcmp(tmp, ">") == 0) {

		res = matchtoken(tmp);
		if(helper(res))
			return;	
		arith();
		strcpy(tmp, currlexem);
	}
}

void arith() {
	char tmp[1024];
	unsigned char res;
	term();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, "+") == 0 || strcmp(tmp, "-") == 0) {
		res = matchtoken(tmp);
		if(helper(res))
			return;	
		term();
		strcpy(tmp, currlexem);
	}
}

void term() {
	char tmp[1024];
	unsigned char res;
	prim();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, "*") == 0 || strcmp(tmp, "/") == 0) {
		res = matchtoken(tmp);
		if(helper(res))
			return;	
		prim();
		strcpy(tmp, currlexem);
	}
}

void prim() {
	char tmp[1024];
	nofunccall();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, "(") == 0) {
		args();
		strcpy(tmp, currlexem);
	}	
}

void nofunccall() {
	char tmp[1024];
	unsigned char res;
	strcpy(tmp, currlexem);
	if(strcmp(tmp, "(") == 0) {
		res = matchtoken("(");
		if(helper(res))
			return;	
		expr();
		res = matchtoken(")");
		if(helper(res))
			return;	
	}
	else if(strcmp(tmp, "let") == 0) {
		res = matchtoken("let");
		if(helper(res))
			return;	
		binds();
		res = matchtoken("in");
		if(helper(res))
			return;	
		expr();
	}
	else if(strcmp(tmp, "letrec") == 0) {
		res = matchtoken("letrec");
		if(helper(res))
			return;	
		binds();
		res = matchtoken("in");
		if(helper(res))
			return;	
		expr();
	}
	else if(strcmp(tmp, "func") == 0) {
		res = matchtoken("func");
		if(helper(res))
			return;	
		params();
		res = matchtoken("{");
		if(helper(res))
			return;	
		expr();
		res = matchtoken("}");
		if(helper(res))
			return;	
	}
	else if(strcmp(tmp, "if") == 0)	{
		res = matchtoken("if");
		if(helper(res))
			return;	
		res = matchtoken("(");
		if(helper(res))
			return;	
		expr();
		res = matchtoken(")");
		if(helper(res))
			return;	
		res = matchtoken("{");
		if(helper(res))
			return;	
		expr();
		res = matchtoken("}");
		if(helper(res))
			return;	
		res = matchtoken("else");
		if(helper(res))
			return;	
		res = matchtoken("{");
		if(helper(res))
			return;	
		expr();
		res = matchtoken("}");
		if(helper(res))
			return;	
	}
	else {
		if(strcmp(tmp, _NOT) == 0) {
			res = matchtoken(_NOT);
			if(helper(res))
				return;	
		}
		else if(strcmp(tmp, _GETSTR) == 0) {
			res = matchtoken(_GETSTR);
			if(helper(res))
				return;	
		}
		else if(strcmp(tmp, _GETINT) == 0) {
			res = matchtoken(_GETINT);
			if(helper(res))
				return;
		}
		else if(strcmp(tmp, _PRINT) == 0) {
			res = matchtoken(_PRINT);
			if(helper(res))
				return;
		}
		else {
			res = matchtoken(tmp);
			if(helper(res))
				return;
		}	
	}	
}

void args() {
	char tmp[1024];
	unsigned char res;
	res = matchtoken("(");
	if(helper(res))
		return;
	strcpy(tmp, currlexem);
	if(strcmp(tmp, ")") == 0) {
		res = matchtoken(")");
		if(helper(res))
			return;
		return;
	}
	strcpy(tmp, currlexem);
	expr();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, ",") == 0) {
		res = matchtoken(",");
		if(helper(res))
			return;
		expr();
		strcpy(tmp, currlexem);
	}
	matchtoken(")");	
}

void params() {
	char tmp[1024];
	unsigned char res;
	strcpy(tmp, currlexem);
	res = matchtoken("(");
	if(helper(res))
		return;
	strcpy(tmp, currlexem);
	if(strcmp(tmp, ")") == 0) {
		res = matchtoken(")");
		if(helper(res))
			return;
		return;
	}
	strcpy(tmp, currlexem);
	res = matchtoken(tmp);
	if(helper(res))
		return;
	strcpy(tmp, currlexem);
	while(strcmp(tmp, ",") == 0) {
		res = matchtoken(",");
		if(helper(res))
			return;
		strcpy(tmp, currlexem);
		res = matchtoken(tmp);
		if(helper(res))
			return;
		strcpy(tmp, currlexem);
	}
	matchtoken(")");	
}

void binds() {
	char tmp[1024];
	unsigned char res;
	strcpy(tmp, currlexem);
	res = matchtoken(tmp);
	if(helper(res))
		return;
	strcpy(tmp, currlexem);
	res = matchtoken("=");
	if(helper(res))
		return;
	expr();
	strcpy(tmp, currlexem);
	while(strcmp(tmp, ",") == 0) {
		res = matchtoken(",");
		if(helper(res))
			return;
		strcpy(tmp, currlexem);
		res = matchtoken(tmp);
		if(helper(res))
			return;
		strcpy(tmp, currlexem);
		res = matchtoken("=");
		if(helper(res))
			return;
		expr();
		strcpy(tmp, currlexem);
	}	
}

